/*
 * Copyright (C) 2011 Johan Maasing johan at zoom.nu Licensed under the Apache
 * License, Version 2.0 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law
 * or agreed to in writing, software distributed under the License is
 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */
package nu.zoom.catonine.swing.tail;

import java.awt.EventQueue;
import java.util.List;

import javax.swing.JTextPane;
import javax.swing.text.DefaultStyledDocument;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import nu.zoom.catonine.tail.TailerListener.LogEntry;
import nu.zoom.gal.progress.Progress;
import nu.zoom.gal.progress.Progress.TaskID;
import nu.zoom.ui.Resources;
import nu.zoom.ui.Resources.ResourceNotFoundException;

/**
 * Helper class to handle thread switching for updating the document with new
 * lines. <b>Not thread safe!</b> must absolutely not be called on the
 * EventThread or by many threads.
 * 
 * @author Johan Maasing
 * 
 */
public class LinesRunner {
	private final Log log = LogFactory.getLog(getClass());
	private static final int MAX_LINES_BEFORE_SWITCHING_DOCUMENTS = 10000;
	private final Progress progress;
	private final String OOME_MESSAGE_LONG;
	private final String OOME_MESSAGE_SHORT;
	private final String INSERTING_TEXT_INTO_DOCUMENT;
	private final JTextPane textPane;
	private final TailingDocument document;
	private TaskID taskID;
	private boolean insertionInProgress = false;
	private boolean run = true;

	public LinesRunner(final Progress progress, final Resources resources, final JTextPane textPane,
			final TailingDocument document) throws ResourceNotFoundException {
		super();
		this.progress = progress;
		this.textPane = textPane;
		this.document = document;
		this.OOME_MESSAGE_LONG = resources.getMessage("nu.zoom.catonine.oome.long");
		this.OOME_MESSAGE_SHORT = resources.getMessage("nu.zoom.catonine.oome.short");
		this.INSERTING_TEXT_INTO_DOCUMENT = resources.getMessage("nu.zoom.catonine.tail.work");

	}

	synchronized void run(final List<LogEntry> lines, final boolean isFollow) {
		insertionInProgress = true;
		taskID = this.progress.createTask();
		this.progress.setIndeterminate(taskID, true);
		prepareeOnEventQueue(lines, isFollow);
		while (insertionInProgress && run) {
			try {
				log.debug("Waiting for insertion to complete");
				wait(4096);
			} catch (InterruptedException e) {
				log.debug("Insertion parent thread interrupted");
				insertionInProgress = false;
				Thread.currentThread().interrupt();
			}
		}
	}

	private synchronized void insertionComplete() {
		log.debug("Insertion completed");
		insertionInProgress = false;
		notifyAll();
	}

	private void prepareeOnEventQueue(final List<LogEntry> lines, final boolean isFollow) {
		EventQueue.invokeLater(new Runnable() {

			@Override
			public void run() {
				if (lines.size() > MAX_LINES_BEFORE_SWITCHING_DOCUMENTS) {
					log.debug("Switching documents, number of lines exceed threshold:" + lines.size() + ">"
							+ MAX_LINES_BEFORE_SWITCHING_DOCUMENTS);
					textPane.setDocument(new DefaultStyledDocument());
					textPane.setText(INSERTING_TEXT_INTO_DOCUMENT);
				}
				insertInBackground(lines, isFollow);
			}
		});
	}

	private void insertInBackground(final List<LogEntry> lines, final boolean isFollow) {
		Thread t = new Thread(new Runnable() {

			@Override
			public void run() {
				try {
					document.lines(lines);
					if (lines.size() > MAX_LINES_BEFORE_SWITCHING_DOCUMENTS) {
						EventQueue.invokeLater(new Runnable() {

							@Override
							public void run() {
								textPane.setDocument(document);
								if (isFollow) {
									textPane.setCaretPosition(document.getLength());
								}
								insertionComplete();
							}
						});
					} else {
						if (isFollow) {
							EventQueue.invokeLater(new Runnable() {

								@Override
								public void run() {
									textPane.setCaretPosition(document.getLength());
									insertionComplete();
								}
							});
						} else {
							insertionComplete();
						}
					}
				} catch (OutOfMemoryError e) {
					log.debug("Out of memory, scheduling document removal");
					EventQueue.invokeLater(new Runnable() {
						@Override
						public void run() {
							textPane.setDocument(new DefaultStyledDocument());
							textPane.setText(OOME_MESSAGE_LONG);
							progress.setMessage(taskID, OOME_MESSAGE_SHORT);
							insertionComplete();
						}
					});
				}
				progress.deleteTask(taskID);
			}
		}, "Document insert thread");
		t.setDaemon(true);
		t.start();
	}

	public synchronized void stop() {
		log.debug("Stopping lines runner");
		run = false;
		notifyAll();
	}
}
